import React, {
	forwardRef,
	useCallback,
	useImperativeHandle,
	useMemo,
	useRef,
	useState,
} from 'react';
import useRefDefaults from '../hooks/use-ref-defaults';
import useUpdateEffect from '../hooks/use-update-effect';
import { noop } from '../utils/misc';
import usePickerDataNames from './hooks/use-picker-data-names';
import usePickerProps from './hooks/use-picker-props';
import {
	PickerCascadeProps,
	PickerCascadeRef,
	PickerOption,
	PickerValue,
} from './interface';
import PickerColumn from './PickerColumn';
import PickerContainer from './PickerContainer';

let PICKER_KEY = 1;

const getValue = (
	_data: PickerOption[],
	size: number,
	valueKey: string,
	childrenKey: string,
	_value?: Array<PickerValue | undefined>,
): Array<PickerValue | undefined> => {
	let data = _data;
	const value = _value || [];

	const output: Array<PickerValue | undefined> = [];

	for (let i = 0; i < size; i++) {
		if (data && data.length) {
			const v = value[i];

			const idx =
				v != null
					? Math.max(
							data.findIndex((d) => d[valueKey] === v),
							0,
					  )
					: 0;

			output[i] = data[idx][valueKey];
			data = data[idx][childrenKey];
		} else {
			output[i] = undefined;
		}
	}

	return output;
};

const getColumns = (
	_data: PickerOption[],
	size: number,
	valueKey: string,
	childrenKey: string,
	value: Array<PickerValue | undefined>,
): Array<PickerOption[]> => {
	let data = _data;

	const output: Array<PickerOption[]> = [];

	for (let i = 0; i < size; i++) {
		if (data && data.length) {
			output.push(data);

			const v = value[i];

			const idx =
				v != null
					? Math.max(
							data.findIndex((d) => d[valueKey] === v),
							0,
					  )
					: 0;
			data = data[idx] && data[idx][childrenKey];
		} else {
			output.push([]);
		}
	}

	return output;
};

const PickerCascade = forwardRef<PickerCascadeRef, PickerCascadeProps>(
	(_props, ref) => {
		const [
			{ optionHeight, visibleOptionsCount, transitionDuration },
			{
				defaultValue,
				data,
				dataNames,
				onChange,
				onConfirm,
				onCancel,
				cascadeSize: _cascadeSize,
				...restProps
			},
		] = usePickerProps(_props);

		const rootRef = useRef<HTMLDivElement>(null);

		const { value: valueKey, children: childrenKey } =
			usePickerDataNames(dataNames);

		const cascadeSize = _cascadeSize || 0;

		const [value, _setValue] = useState(() =>
			getValue(
				data || [],
				cascadeSize,
				valueKey,
				childrenKey,
				defaultValue,
			),
		);

		const valueRef = useRefDefaults(value);
		const onChangeRef = useRefDefaults(onChange || noop);

		const pickerCascadeRef = useMemo<PickerCascadeRef>(
			() => ({
				root: rootRef.current,
				getValue: () => valueRef.current,
			}),
			[valueRef],
		);

		useImperativeHandle<PickerCascadeRef, PickerCascadeRef>(
			ref,
			() => pickerCascadeRef,
			[pickerCascadeRef],
		);

		const setValue = useCallback(
			(v: Array<PickerValue | undefined>) => {
				valueRef.current = v;
				_setValue(v);
			},
			[valueRef],
		);

		const [columns, setColumns] = useState<Array<[number, PickerOption[]]>>(
			() =>
				getColumns(
					Array.isArray(data) ? data : [],
					cascadeSize,
					valueKey,
					childrenKey,
					value,
				).map((col) => [0, col]),
		);

		const emit = (
			listener?: (v: Array<PickerValue | undefined>) => void,
		) => {
			if (!listener) {
				return;
			}

			listener(value);
		};

		const onColumnChange = (
			options: PickerOption[],
			optionIndex: number,
			level: number,
		) => {
			const children =
				options[optionIndex] && options[optionIndex][childrenKey];

			const valuePart = getValue(
				children,
				cascadeSize - 1 - level,
				valueKey,
				childrenKey,
			);

			setValue([
				...value.slice(0, level),
				options[optionIndex][valueKey],
				...valuePart,
			]);

			const key = PICKER_KEY++;

			const columnPart = getColumns(
				children,
				cascadeSize - 1 - level,
				valueKey,
				childrenKey,
				valuePart,
			).map<[number, PickerOption[]]>((cols) => [key, cols]);

			setColumns([...columns.slice(0, level + 1), ...columnPart]);
		};

		useUpdateEffect(() => {
			const nextValue = getValue(
				data || [],
				cascadeSize,
				valueKey,
				childrenKey,
				valueRef.current,
			);

			setValue(nextValue);

			setColumns(
				getColumns(
					data || [],
					cascadeSize,
					valueKey,
					childrenKey,
					valueRef.current,
				).map((col) => [0, col]),
			);
		}, [cascadeSize, childrenKey, data, setValue, valueKey, valueRef]);

		useUpdateEffect(() => {
			onChangeRef.current(value);
		}, [onChangeRef, value]);

		return (
			<PickerContainer
				{...restProps}
				optionHeight={optionHeight}
				visibleOptionsCount={visibleOptionsCount}
				ref={rootRef}
				onConfirm={() => emit(onConfirm)}
				onCancel={() => emit(onCancel)}
			>
				{columns.map(([key, options], idx) => {
					return (
						<PickerColumn
							key={`${key}-${idx}`}
							optionHeight={optionHeight}
							visibleOptionsCount={visibleOptionsCount}
							transitionDuration={transitionDuration}
							defaultValue={value[idx]}
							data={options}
							dataNames={dataNames}
							onChange={(_, cIdex) =>
								onColumnChange(options, cIdex, idx)
							}
						/>
					);
				})}
			</PickerContainer>
		);
	},
);

PickerCascade.displayName = 'PickerMulti';

export default PickerCascade;
